package com.dragon.common.utils.io.compress;

import cn.hutool.core.io.FileUtil;
import com.dragon.common.utils.StringUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Objects;

/**
 * Zip工具类
 *
 * @author dragon
 * @version 1.0
 * @date 2024/8/28 18:12
 */
public class ZipUtils {

    private static final Logger log = LoggerFactory.getLogger(ZipUtils.class);

    /**
     * 压缩文件/目录
     *
     * @param srcPath 文件/目录
     * @param zipPath 压缩后的文件路径
     */
    public static void zip(String srcPath, String zipPath) {
        if (StringUtils.isBlank(srcPath)) {
            throw new RuntimeException("Source file or directory must not be blank !");
        }
        if (StringUtils.isBlank(zipPath)) {
            throw new RuntimeException("Zip file must not be blank !");
        }
        if (!StringUtils.endsWithIgnoreCase(zipPath, ".zip")) {
            throw new RuntimeException("Zip file must end with .zip !");
        }

        // 压缩文件
        File zipFile = new File(zipPath);
        // 源文件
        File srcFile = new File(srcPath);

        zip(srcFile, zipFile);
    }

    /**
     * 压缩文件/目录
     *
     * @param srcPath      文件/目录
     * @param outputStream 压缩文件输出流
     */
    public static void zip(String srcPath, OutputStream outputStream) {
        if (StringUtils.isBlank(srcPath)) {
            throw new RuntimeException("Source file or directory must not be blank !");
        }
        if (outputStream == null) {
            throw new RuntimeException("OutputStream must not be null !");
        }

        // 源文件
        File srcFile = new File(srcPath);

        zip(srcFile, outputStream);
    }

    /**
     * 压缩文件/目录
     *
     * @param srcFile 文件/目录
     * @param zipFile 压缩后的文件
     */
    public static void zip(File srcFile, File zipFile) {
        try {
            zip(srcFile, new FileOutputStream(zipFile));
        } catch (Exception e) {
            log.error("ZipUtils {}", e.getMessage(), e);
        }
    }

    /**
     * 压缩文件/目录
     *
     * @param srcFile      文件/目录
     * @param outputStream 压缩文件输出流
     */
    public static void zip(File srcFile, OutputStream outputStream) {
        if (!srcFile.exists()) {
            throw new RuntimeException(StringUtils.format("Source file or directory [{}] not exist !", srcFile.getAbsolutePath()));
        }

        // 获取源文件列表
        Collection<File> srcFiles = FileUtils.listFilesAndDirs(srcFile, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
        // 空目录列表
        Collection<File> emptyDirectories = srcFiles.stream().filter(File::isDirectory).filter(dir -> Objects.requireNonNull(dir.listFiles()).length < 1).toList();

        validateFiles(new File("demo.zip"), srcFiles);

        ZipArchiveOutputStream zipOutput = null;
        try {
            // zipOutput = (ZipArchiveOutputStream) new ArchiveStreamFactory()
            //     .createArchiveOutputStream(ArchiveStreamFactory.ZIP, outputStream);
            // zipOutput.setEncoding("utf-8");
            // zipOutput.setUseZip64(Zip64Mode.AsNeeded);

            zipOutput = new ZipArchiveOutputStream(outputStream);

            for (File file : srcFiles) {
                // 如果压缩目录不是空目录，则跳过
                if (file.isDirectory() && !emptyDirectories.contains(file)) {
                    continue;
                }

                InputStream inputStream = null;
                try {
                    String relativePath = file.getName();
                    if (srcFile.isDirectory()) {
                        // 如果压缩文件为目录，改为相对路径
                        relativePath = StringUtils.replace(file.getPath(), srcFile.getPath() + File.separator, StringUtils.EMPTY);
                    }

                    ZipArchiveEntry zipEntry = new ZipArchiveEntry(file, relativePath);
                    zipOutput.putArchiveEntry(zipEntry);

                    if (file.isDirectory()) {
                        continue;
                    }

                    inputStream = new FileInputStream(file);
                    // 将文件写入 ZipOutputStream
                    IOUtils.copy(inputStream, zipOutput);
                } catch (Exception e) {
                    log.error("ZipUtils {}", e.getMessage(), e);
                } finally {
                    IOUtils.closeQuietly(inputStream);
                    // 完成 ZipEntry 的写入
                    zipOutput.closeArchiveEntry();
                }
            }
        } catch (Exception e) {
            log.error("ZipUtils {}", e.getMessage(), e);
        } finally {
            IOUtils.closeQuietly(zipOutput);
        }
    }

    /**
     * 解压文件
     *
     * @param zipFilePath 压缩文件路径
     * @param outFileDir  解压后的文件路径
     * @return 解压后的文件
     */
    public static File unzip(String zipFilePath, String outFileDir) {
        if (StringUtils.isBlank(zipFilePath)) {
            throw new RuntimeException("Zip file must not be blank !");
        }
        if (StringUtils.isBlank(outFileDir)) {
            throw new RuntimeException("Output file directory must not be blank !");
        }

        return unzip(new File(zipFilePath), new File(outFileDir));
    }

    /**
     * 解压文件
     *
     * @param zipFile 压缩文件
     * @param outFile 解压后的文件
     * @return 解压后的文件
     */
    public static File unzip(File zipFile, File outFile) {
        if (!zipFile.exists()) {
            throw new RuntimeException(StringUtils.format("Zip file [{}] not exist !", zipFile.getAbsolutePath()));
        }
        if (outFile.isFile()) {
            throw new RuntimeException(StringUtils.format("Output file [{}] must not be a file !", outFile.getAbsolutePath()));
        }

        if (!outFile.exists()) {
            boolean mkdirFlag = outFile.mkdirs();
            if (!mkdirFlag) {
                throw new RuntimeException(StringUtils.format("Create output file directory [{}] failed !", outFile.getAbsolutePath()));
            }
        }

        ZipArchiveInputStream zipInputStream = null;
        try {
            zipInputStream = new ZipArchiveInputStream(new FileInputStream(zipFile));
            ZipArchiveEntry zipEntry;
            FileOutputStream fileOutputStream = null;

            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                try {
                    File file = new File(outFile, zipEntry.getName());

                    if (zipEntry.isDirectory()) {
                        // 若为目录
                        boolean mkdirFlag = file.mkdirs();
                        continue;
                    }

                    // 对文件的处理
                    //如果指定文件的目录不存在,则创建之.
                    File parent = file.getParentFile();
                    if (!parent.exists()) {
                        boolean mkdirFlag = parent.mkdirs();
                    }

                    fileOutputStream = new FileOutputStream(file);
                    IOUtils.copy(zipInputStream, fileOutputStream);
                } catch (Exception e) {
                    log.error("ZipUtils {}", e.getMessage(), e);
                } finally {
                    IOUtils.closeQuietly(fileOutputStream);
                }
            }
        } catch (Exception e) {
            // Truncated ZIP file
            // Unexpected end of ZLIB input stream 解压到最后一个条目如果是空文件夹会异常关闭
            // 最终发现是压缩成ZIP文件的时候没有正常关闭 ArchiveEntry 导致解压异常，如果实在想处理则捕捉 EOFException/IOException
            log.error("ZipUtils {}", e.getMessage(), e);
        } finally {
            IOUtils.closeQuietly(zipInputStream);
        }

        return outFile;
    }

    /**
     * 验证文件
     *
     * @param zipFile  压缩文件
     * @param srcFiles 需要压缩文件列表
     */
    private static void validateFiles(File zipFile, Iterable<File> srcFiles) {
        if (zipFile == null) {
            throw new RuntimeException("Zip file must not be null !");
        }
        if (zipFile.isDirectory()) {
            throw new RuntimeException(StringUtils.format("Zip file [{}] must not be a directory !", zipFile.getAbsoluteFile()));
        } else {
            for (File srcFile : srcFiles) {
                if (srcFile != null) {
                    if (!srcFile.exists()) {
                        throw new RuntimeException(StringUtils.format("File [{}] not exist !", srcFile.getAbsolutePath()));
                    }

                    File parentFile;
                    try {
                        parentFile = zipFile.getCanonicalFile().getParentFile();
                    } catch (IOException var8) {
                        parentFile = zipFile.getParentFile();
                    }

                    if (srcFile.isDirectory() && FileUtil.isSub(srcFile, parentFile)) {
                        throw new RuntimeException(StringUtils.format("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getPath(), srcFile.getPath()));
                    }
                }
            }
        }
    }

}
